#include "config.h"

#include "ms2lstat.h"

#include <math.h>

#define ACCESS_HOURZONE(access)	(((access)->tick_start % 86400) / 3600)
#define ACCESS_NDAY(access)	(((access)->tick_start / 86400))
#define HOURZONE_DISABLED()	(start_hourzone == 0 && end_hourzone == 0)

ecm_uint32	arrival_duration = 600;
ecm_uint32	start_hourzone, end_hourzone;
ecm_int32	exclude_day;

ecm_uint32	max_hourly_n_arrivals;

static ecm_uint32	n_arrivals;
static ecm_uint32	peak_hour;

static LIST_HEAD(arrivals);
static hist_t	*hist_hourly_arrivals;
static hist_t	*hist_daily_arrivals;
static hist_t	*hist_weekly_arrivals;

static access_t *
get_head_access(session_t *session)
{
	access_t	*access;

	ECMASSERT(!list_empty(&session->accesses));
	access = list_entry(session->accesses.next, access_t, list);
	return access;
}

static void
incr_time_n_arrivals(access_t *access)
{
	ecm_uint32	hourzone = ACCESS_HOURZONE(access);
	ecm_uint32	nday = ACCESS_NDAY(access);

	add_hist(hist_hourly_arrivals, hourzone, 1);
	if (hist_hourly_arrivals->data[hourzone] > max_hourly_n_arrivals) {
		max_hourly_n_arrivals = hist_hourly_arrivals->data[hourzone];
		peak_hour = hourzone;
	}
	if (hourzone >= 24) {
		ECMASSERT(0);
	}
	add_hist(hist_daily_arrivals, nday, 1);
	add_hist(hist_weekly_arrivals, (wday_start + nday) % 7, 1);
}

ecm_int32
get_session_interarrival(session_t *session)
{
	session_t	*prev;
	access_t	*access, *prev_access;

	if (session->list_ia.prev == &arrivals)
		return -1;

	access = get_head_access(session);
	prev = list_entry(session->list_ia.prev, session_t, list_ia);
	prev_access = get_head_access(prev);

	ECMASSERT(access->tick_start >= prev_access->tick_start);

	return (access->tick_start - prev_access->tick_start);
}

ecm_double
get_media_peak_interarrival(ecm_uint32 mid)
{
	session_t	*session;
	ecm_uint32	n_ia = 0;
	ecm_uint32	total_ia = 0;
	ecm_uint32	last_tick = 0;
	access_t	*access;
	ecm_uint32	hour;
	struct list_head	*lp;

	list_for_each (lp, &arrivals) {
		session = list_entry(lp, session_t, list_ia);
		if (session->mid != mid)
			continue;
		access = get_head_access(session);
		hour = ACCESS_HOURZONE(access);
		if (hour != peak_hour) {
			last_tick = 0;
			continue;
		}
		if (last_tick > 0) {
			total_ia += (access->tick_start - last_tick);
			n_ia++;
		}
		last_tick = access->tick_start;
	}
	if (n_ia == 0)
		return -1;
	return ((ecm_double)total_ia) / n_ia;
}

void
add_arrival(session_t *session)
{
	session_t	*session_list;
	access_t	*access, *access_list;
	struct list_head	*lp;

	access = get_head_access(session);
	for (lp = arrivals.prev; lp != &arrivals; lp = lp->prev) {
		session_list = list_entry(lp, session_t, list_ia);

		access_list = get_head_access(session_list);
		
		if (access->tick_start >= access_list->tick_start) {
			list_add(&session->list_ia, &session_list->list_ia);
			goto out;
		}
	}
	list_add(&session->list_ia, &arrivals);
out:
	n_arrivals++;
	incr_time_n_arrivals(access);
	incr_media_access(session);
}

void
del_arrival(session_t *session)
{
	list_del_init(&session->list_ia);
}

static void
setup_arrival_hist(hist_t **phist_inter_arrivals, hist_t **phist_arrival_freq)
{
	hist_t		*hist_inter_arrivals;
	hist_t		*hist_arrival_freq;
	ecm_uint32	tick_last = 0;
	ecm_uint32	tick_last_mon = 0;
	ecm_uint32	n_arrivals_mon = 0;
	struct list_head	*lp;

	hist_inter_arrivals = create_hist();
	hist_arrival_freq = create_hist();

	list_for_each (lp, &arrivals) {
		session_t	*session;
		access_t	*access;
		ecm_uint32	hourzone;
		ecm_uint32	ia;

		session = list_entry(lp, session_t, list_ia);
		access = get_head_access(session);
		hourzone = ACCESS_HOURZONE(access);

		if (HOURZONE_DISABLED() ||
		    (exclude_day != (ACCESS_NDAY(access) + 1) &&
		     (hourzone >= start_hourzone && hourzone <= end_hourzone))) {
			n_arrivals_mon++;
			if (tick_last_mon == 0)
				tick_last_mon = tick_last;
		}

		if (tick_last > 0) {
			ECMASSERT(access->tick_start >= tick_last);
			ia = access->tick_start - tick_last;

			add_hist(hist_inter_arrivals, ia, 1);

			if (HOURZONE_DISABLED() ||
			    (exclude_day != (ACCESS_NDAY(access) + 1) &&
			     (hourzone >= start_hourzone && hourzone <= end_hourzone))) {
				ecm_double	elapsed;

				elapsed = tick_last - tick_last_mon;
				while (elapsed >= arrival_duration) {
					add_hist(hist_arrival_freq, n_arrivals_mon, 1);
					n_arrivals_mon = 0;
					elapsed -= arrival_duration;
					tick_last_mon += arrival_duration;
				}
			}
		}
		if (HOURZONE_DISABLED() ||
		    (exclude_day != (ACCESS_NDAY(access) + 1) &&
		     (hourzone >= start_hourzone && hourzone <= end_hourzone))) {
			tick_last = access->tick_start;
		}
		else {
			tick_last = 0;
			tick_last_mon = 0;
			n_arrivals_mon = 0;
		}
	}

	*phist_inter_arrivals = hist_inter_arrivals;
	*phist_arrival_freq = hist_arrival_freq;
}

#define N_VALID_ARRIVAL_CUTOFF	0.001

static ecm_uint32
get_n_valid_arrivals(hist_t *hist_inter_arrivals)
{
	ecm_uint32	n_valid_arrivals = 0;
	ecm_uint32	i;

	for (i = 0; i <= hist_inter_arrivals->n_max; i++) {
		ecm_uint32	count = hist_inter_arrivals->data[i];

		if (((ecm_double)count / n_arrivals) < N_VALID_ARRIVAL_CUTOFF)
			continue;
		n_valid_arrivals += count;
	}

	return n_valid_arrivals;
}

static ecm_double
factorial(ecm_uint32 n)
{
	ecm_double	value = 1;
	ecm_uint32	i;

	if (n == 1 || n == 0)
		return 1;
	for (i = n; i > 1; i--)
		value *= i;
	return value;
}

static void
report_arrival_freq(hist_t *hist_arrival_freq)
{
	FILE	*fp_arrival_freq;
	FILE	*fp_arrival_poisson;
	ecm_uint32	i;
	ecm_uint32	sum = 0;
	ecm_double	lambda;

	fp_arrival_freq = setup_logging("arrival_freq");
	fp_arrival_poisson = setup_logging("arrival_poisson");

	ecm_fprintf(fp_arrival_freq, "# freq prob count\n");

	for (i = 0; i <= hist_arrival_freq->n_max; i++) {
		ecm_uint32	count = hist_arrival_freq->data[i];
		ecm_double	prob;

		sum += (count * i);
		prob = ((ecm_double)count) / hist_arrival_freq->n_count;
		ecm_fprintf(fp_arrival_freq, "%6u %lf %u\n", i, prob, count);
	}

	lambda = ((ecm_double)sum) / hist_arrival_freq->n_count;
	ecm_fprintf(fp_arrival_poisson, "# freq poisson_prob\n");
	ecm_fprintf(fp_arrival_poisson, "# lambda: %lf\n", lambda);

	for (i = 0; i <= hist_arrival_freq->n_max; i++) {
		ecm_double	prob;

		prob = pow(lambda, i) * (exp(-lambda) / factorial(i));
		ecm_fprintf(fp_arrival_poisson, "%6u %lf\n", i, prob);
	}

	ecm_fclose(fp_arrival_freq);
	ecm_fclose(fp_arrival_poisson);
}

static void
report_inter_arrival(FILE *fp, hist_t *hist_inter_arrivals)
{
	FILE		*fp_ia;
	FILE		*fp_ia_rate_valid, *fp_ia_rate_full;
	FILE		*fp_ia_rate_valid_cdf, *fp_ia_rate_full_cdf;
	ecm_uint32	sum = 0;
	ecm_uint32	sum_valid = 0;
	ecm_uint32	n_valid_arrivals;
	ecm_uint32	i;

	fp_ia = setup_logging("arrival");
	fp_ia_rate_valid = setup_logging("arrival_rate_valid");
	fp_ia_rate_valid_cdf = setup_logging("arrival_rate_valid_cdf");
	fp_ia_rate_full = setup_logging("arrival_rate_full");
	fp_ia_rate_full_cdf = setup_logging("arrival_rate_full_cdf");

	n_valid_arrivals = get_n_valid_arrivals(hist_inter_arrivals);

	ecm_fprintf(fp_ia, "# inter-arrival count\n");
	ecm_fprintf(fp_ia_rate_full, "# inter-arrival-rate-full prob\n");
	ecm_fprintf(fp_ia_rate_valid, "# inter-arrival-rate-valid  prob\n");
	ecm_fprintf(fp_ia_rate_full_cdf, "# inter-arrival-rate-full-cdf prob(CDF)\n");
	ecm_fprintf(fp_ia_rate_valid_cdf, "# inter-arrival-rate-valid-cdf prob(CDF)\n");
	for (i = 0; i <= hist_inter_arrivals->n_max; i++) {
		ecm_uint32	count = hist_inter_arrivals->data[i];

		if (count > 0) {
			ecm_double	prob, accu_prob;

			sum += count;

			prob = ((ecm_double)count) / (n_arrivals - 1);
			accu_prob = ((ecm_double)sum) / (n_arrivals - 1);

			ecm_fprintf(fp_ia, "%8u %u\n", i, count);

			ecm_fprintf(fp_ia_rate_full, "%8u %lf\n", i, prob);
			ecm_fprintf(fp_ia_rate_full_cdf, "%8u %lf\n", i, accu_prob);
		}
		if (((ecm_double)count) / n_arrivals >= N_VALID_ARRIVAL_CUTOFF) {
			ecm_double	prob, accu_prob;

			sum_valid += count;

			prob = ((ecm_double)count) / n_valid_arrivals;
			accu_prob = ((ecm_double)sum_valid) / n_valid_arrivals;
			ecm_fprintf(fp_ia_rate_valid, "%8u %lf\n", i, prob);
			ecm_fprintf(fp_ia_rate_valid_cdf, "%8u %lf\n", i, accu_prob);
		}
	}

	ecm_fclose(fp_ia);
	ecm_fclose(fp_ia_rate_valid);
	ecm_fclose(fp_ia_rate_full);
	ecm_fclose(fp_ia_rate_valid_cdf);
	ecm_fclose(fp_ia_rate_full_cdf);
}

static void
report_hourly_arrival(void)
{
	FILE	*fp_hourly;
	ecm_uint32	count;
	ecm_uint32	i;

	fp_hourly = setup_logging("arrival_hourly");
	ecm_fprintf(fp_hourly, "# hourzone count prob\n");
	for (i = 0; i <= hist_hourly_arrivals->n_max; i++) {
		count = hist_hourly_arrivals->data[i];
		ecm_fprintf(fp_hourly, "%02u %d %lf\n", i, count,
			    ((ecm_double)count) / hist_hourly_arrivals->n_count);
	}
	ecm_fclose(fp_hourly);
}

static void
report_daily_arrival(void)
{
	FILE	*fp_daily;
	ecm_uint32	count;
	ecm_uint32	i;

	fp_daily = setup_logging("arrival_daily");
	ecm_fprintf(fp_daily, "# nday count wday(Sun0,Sat6) prob\n");
	for (i = 0; i <= hist_daily_arrivals->n_max; i++) {
		count = hist_daily_arrivals->data[i];
		ecm_fprintf(fp_daily, "%6u %d %d %lf\n", i + 1, count, (wday_start + i) % 7,
			    ((ecm_double)count) / hist_daily_arrivals->n_count);
	}
	ecm_fclose(fp_daily);
}

static void
report_weekly_arrival(void)
{
	FILE	*fp_weekly;
	ecm_uint32	count;
	ecm_uint32	i;

	fp_weekly = setup_logging("arrival_weekly");
	ecm_fprintf(fp_weekly, "# week(Sun0,Sat6) count prob\n");
	for (i = 0; i <= hist_weekly_arrivals->n_max; i++) {
		count = hist_weekly_arrivals->data[i];
		ecm_fprintf(fp_weekly, "%u %d %lf\n", i, count,
			    ((ecm_double)count) / hist_weekly_arrivals->n_count);
	}
	ecm_fclose(fp_weekly);
}

static void
report_arrival_misc(void)
{
	struct list_head	*lp;
	ecm_uint32	tick_last = 0;
	FILE	*fp_arrival_misc;

	fp_arrival_misc = setup_logging("arrival_misc");

	ecm_fprintf(fp_arrival_misc, "# interarrival media-length media-rank sesslen interseek\n");

	list_for_each (lp, &arrivals) {
		session_t	*session;
		access_t	*access;
		ecm_uint32	ia;

		session = list_entry(lp, session_t, list_ia);
		access = get_head_access(session);
		if (tick_last > 0) {
			media_t	*media = get_media(session->mid);
			ecm_uint32	rank = get_media_rank(session->mid);

			ia = access->tick_start - tick_last;
			ecm_fprintf(fp_arrival_misc, "%6u %6u %6u %.2lf %.2lf\n", ia,
				    media->length, rank,
				    (ecm_double)get_sesslen(session) / media->length * 100,
				    get_interseek_time(session));
		}
		tick_last = access->tick_start;
	}

	ecm_fclose(fp_arrival_misc);
}

void
report_arrival_result(FILE *fp)
{
	hist_t		*hist_inter_arrivals;
	hist_t		*hist_arrival_freq;

	report_arrival_misc();

	setup_arrival_hist(&hist_inter_arrivals, &hist_arrival_freq);

	report_inter_arrival(fp, hist_inter_arrivals);
	report_arrival_freq(hist_arrival_freq);
	report_hourly_arrival();
	report_daily_arrival();
	report_weekly_arrival();

	free_hist(hist_inter_arrivals);
	free_hist(hist_arrival_freq);
}

ECMBOOL
init_arrival(void)
{
	hist_hourly_arrivals = create_hist();
	hist_daily_arrivals = create_hist();
	hist_weekly_arrivals = create_hist();
	return ECM_TRUE;
}
